package top.yangguangmc.sunshine_anticheat;

import org.bukkit.OfflinePlayer;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.yangguangmc.sunshine_anticheat.check.Check;
import top.yangguangmc.sunshine_anticheat.check.CheckSetting;
import top.yangguangmc.sunshine_anticheat.check.action.Action;
import top.yangguangmc.sunshine_anticheat.utils.Utils;

import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import static top.yangguangmc.sunshine_anticheat.utils.Builtin.*;

public class ConfigManager {
    private final FileConfiguration config;
    private final FileConfiguration defaultConfig;

    @SuppressWarnings("deprecation")
    ConfigManager() {
        config = SunshineAntiCheat.getInstance().getConfig();
        try {
            defaultConfig = new YamlConfiguration();
            defaultConfig.load(new InputStreamReader(SunshineAntiCheat.getInstance().getResource("config.yml")));
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (InvalidConfigurationException e) {
            throw new AssertionError(e);    // shouldn't happen
        }
        config.setDefaults(defaultConfig);
    }

    @SuppressWarnings("deprecation")
    public void load() {
        SunshineAntiCheat.getInstance().reloadConfig();
        SunshineAntiCheat.getInstance().getCheckManager().loadConfig();
    }

    public boolean resetConfig() {
        boolean success = getFile().delete();
        ssac.saveDefaultConfig();
        load();
        return success;
    }

    public @NotNull FileConfiguration getConfig() {
        return config;
    }

    public File getFile() {
        return Paths.get(SunshineAntiCheat.getInstance().getDataFolder().getAbsolutePath(), "config.yml").toFile();
    }

    public @NotNull String getString(String key) {
        return getString(key, null);
    }

    public @NotNull String getString(String key, @Nullable OfflinePlayer player) {
        String s;
        if (config.isSet(key)) {
            if (config.isString(key))
                s = config.getString(key);
            else {
                logger.warning("Invalid value of config key '" + key + "': string expected!");
                s = defaultConfig.getString(key);
            }
        } else {
            logger.warning("Missing config key: " + key);
            s = defaultConfig.getString(key);
        }
        s = Utils.preprocessString(s, player);
        return s;
    }

    public int getInt(String key) {
        if (config.isSet(key)) {
            if (config.isInt(key))
                return config.getInt(key);
            else {
                logger.warning("Invalid value of config key '" + key + "': int expected!");
                return defaultConfig.getInt(key);
            }
        } else {
            logger.warning("Missing config key: " + key);
            return defaultConfig.getInt(key);
        }
    }

    public boolean getBoolean(String key) {
        if (config.isSet(key)) {
            if (config.isBoolean(key))
                return config.getBoolean(key);
            else {
                logger.warning("Invalid value of config key '" + key + "': boolean expected!");
                return defaultConfig.getBoolean(key);
            }
        } else {
            logger.warning("Missing config key: " + key);
            return defaultConfig.getBoolean(key);
        }
    }

    public double getDouble(String key) {
        if (config.isSet(key)) {
            if (config.isDouble(key))
                return config.getDouble(key);
            else {
                logger.warning("Invalid value of config key '" + key + "': int expected!");
                return defaultConfig.getDouble(key);
            }
        } else {
            logger.warning("Missing config key: " + key);
            return defaultConfig.getDouble(key);
        }
    }

    public long getLong(String key) {
        if (config.isSet(key)) {
            if (config.isLong(key))
                return config.getLong(key);
            else {
                logger.warning("Invalid value of config key '" + key + "': int expected!");
                return defaultConfig.getLong(key);
            }
        } else {
            logger.warning("Missing config key: " + key);
            return defaultConfig.getLong(key);
        }
    }

    public @Nullable Object get(String key) {
        if (config.isSet(key))
            return config.get(key);
        else {
            logger.warning("Missing config key: " + key);
            return defaultConfig.get(key);
        }
    }

    public @Nullable List<?> getList(String key) {
        if (config.isSet(key)) {
            if (config.isList(key))
                return config.getList(key);
            else {
                logger.warning("Invalid value of config key '" + key + "': int expected!");
                return defaultConfig.getList(key);
            }
        } else {
            logger.warning("Missing config key: " + key);
            return defaultConfig.getList(key);
        }
    }

    public List<Action> getActionList(String key) {
        List<String> list = config.getStringList(key);
        LinkedList<Action> actions = new LinkedList<>();
        int i = 1;
        for (String line : list) {
            if (line.startsWith("[~] ")) {
                if (i != 1)
                    actions.getLast().getValue().add(line.substring(4));
                else {
                    logger.warning("Invalid action list of config key '" + key + "', line " + i + ": The list cannot start with '[~]'!");
                }
            } else {
                try {
                    actions.add(Action.of(line));
                } catch (ParseException e) {
                    if (e.getErrorOffset() >= 0) {
                        logger.warning("Invalid action list of config key '" + key + "', line " + i + ", at index " + e.getErrorOffset() + ": " + e.getMessage());
                    } else {
                        logger.warning("Invalid action list of config key '" + key + "', line " + i + ": " + e.getMessage());
                    }
                }
            }
            i++;
        }
        actions.sort(null);
        return actions;
    }

    public void verifyConfig() {
        List<String> configNodes = new ArrayList<>(Arrays.asList("plugin-prefix", "no-permission-msg", "alert-message",
                "vl-resetting.reset-by-interval", "vl-resetting.reset-interval", "vl-resetting.reset-on-death",
                "vl-resetting.reset-on-rejoin"));
        for (Check check : SunshineAntiCheat.getInstance().getCheckManager().getChecks()) {
            String path = "checks." + check.getCategory().name().toLowerCase() + "." + check.getConfigName() + ".";
            configNodes.add(path + "enabled");
            for (Field field : check.getClass().getDeclaredFields()) {
                CheckSetting ann = field.getAnnotation(CheckSetting.class);
                if (ann != null) {
                    configNodes.add(path + ann.configName());
                }
            }
        }
        for (String configNode : configNodes) {
            if (!config.isSet(configNode))
                logger.warning("Missing config node \"" + configNode + "\" in config.yml!");
        }
    }
}
